{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-02-08T02:28:15.315946Z",
     "start_time": "2025-02-08T02:28:13.362218Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.3.1+cu121\n",
      "cuda:0\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 准备数据",
   "id": "30533b3839955b71"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-08T02:28:23.045322Z",
     "start_time": "2025-02-08T02:28:23.001808Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.datasets import fetch_california_housing\n",
    "\n",
    "housing = fetch_california_housing()\n",
    "print(housing.DESCR)\n",
    "print(housing.data.shape)\n",
    "print(housing.target.shape)"
   ],
   "id": "c6a588cf54e12e26",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      ".. _california_housing_dataset:\n",
      "\n",
      "California Housing dataset\n",
      "--------------------------\n",
      "\n",
      "**Data Set Characteristics:**\n",
      "\n",
      ":Number of Instances: 20640\n",
      "\n",
      ":Number of Attributes: 8 numeric, predictive attributes and the target\n",
      "\n",
      ":Attribute Information:\n",
      "    - MedInc        median income in block group\n",
      "    - HouseAge      median house age in block group\n",
      "    - AveRooms      average number of rooms per household\n",
      "    - AveBedrms     average number of bedrooms per household\n",
      "    - Population    block group population\n",
      "    - AveOccup      average number of household members\n",
      "    - Latitude      block group latitude\n",
      "    - Longitude     block group longitude\n",
      "\n",
      ":Missing Attribute Values: None\n",
      "\n",
      "This dataset was obtained from the StatLib repository.\n",
      "https://www.dcc.fc.up.pt/~ltorgo/Regression/cal_housing.html\n",
      "\n",
      "The target variable is the median house value for California districts,\n",
      "expressed in hundreds of thousands of dollars ($100,000).\n",
      "\n",
      "This dataset was derived from the 1990 U.S. census, using one row per census\n",
      "block group. A block group is the smallest geographical unit for which the U.S.\n",
      "Census Bureau publishes sample data (a block group typically has a population\n",
      "of 600 to 3,000 people).\n",
      "\n",
      "A household is a group of people residing within a home. Since the average\n",
      "number of rooms and bedrooms in this dataset are provided per household, these\n",
      "columns may take surprisingly large values for block groups with few households\n",
      "and many empty houses, such as vacation resorts.\n",
      "\n",
      "It can be downloaded/loaded using the\n",
      ":func:`sklearn.datasets.fetch_california_housing` function.\n",
      "\n",
      ".. rubric:: References\n",
      "\n",
      "- Pace, R. Kelley and Ronald Barry, Sparse Spatial Autoregressions,\n",
      "  Statistics and Probability Letters, 33 (1997) 291-297\n",
      "\n",
      "(20640, 8)\n",
      "(20640,)\n"
     ]
    }
   ],
   "execution_count": 2
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-08T02:28:27.830603Z",
     "start_time": "2025-02-08T02:28:27.750488Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.model_selection import train_test_split\n",
    "\n",
    "#拆分训练集和测试集，random_state是随机种子,同样的随机数种子，是为了得到同样的随机值\n",
    "x_train_all, x_test, y_train_all, y_test = train_test_split(\n",
    "    housing.data, housing.target, random_state=7)\n",
    "x_train, x_valid, y_train, y_valid = train_test_split(\n",
    "    x_train_all, y_train_all, random_state=11)\n",
    "# 训练集\n",
    "print(x_train.shape, y_train.shape)\n",
    "# 验证集\n",
    "print(x_valid.shape, y_valid.shape)\n",
    "# 测试集\n",
    "print(x_test.shape, y_test.shape)\n",
    "\n",
    "dataset_maps = {\n",
    "    \"train\": [x_train, y_train],\n",
    "    \"valid\": [x_valid, y_valid],\n",
    "    \"test\": [x_test, y_test],\n",
    "}\n"
   ],
   "id": "3ddb45d9a66f5248",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "(11610, 8) (11610,)\n",
      "(3870, 8) (3870,)\n",
      "(5160, 8) (5160,)\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-08T02:28:31.878459Z",
     "start_time": "2025-02-08T02:28:31.871516Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.preprocessing import StandardScaler\n",
    "from torch.utils.data import DataLoader\n",
    "\n",
    "scaler = StandardScaler()\n",
    "scaler.fit(x_train)"
   ],
   "id": "dd4da3f50e1c8d83",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "StandardScaler()"
      ],
      "text/html": [
       "<style>#sk-container-id-1 {\n",
       "  /* Definition of color scheme common for light and dark mode */\n",
       "  --sklearn-color-text: #000;\n",
       "  --sklearn-color-text-muted: #666;\n",
       "  --sklearn-color-line: gray;\n",
       "  /* Definition of color scheme for unfitted estimators */\n",
       "  --sklearn-color-unfitted-level-0: #fff5e6;\n",
       "  --sklearn-color-unfitted-level-1: #f6e4d2;\n",
       "  --sklearn-color-unfitted-level-2: #ffe0b3;\n",
       "  --sklearn-color-unfitted-level-3: chocolate;\n",
       "  /* Definition of color scheme for fitted estimators */\n",
       "  --sklearn-color-fitted-level-0: #f0f8ff;\n",
       "  --sklearn-color-fitted-level-1: #d4ebff;\n",
       "  --sklearn-color-fitted-level-2: #b3dbfd;\n",
       "  --sklearn-color-fitted-level-3: cornflowerblue;\n",
       "\n",
       "  /* Specific color for light theme */\n",
       "  --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, white)));\n",
       "  --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, black)));\n",
       "  --sklearn-color-icon: #696969;\n",
       "\n",
       "  @media (prefers-color-scheme: dark) {\n",
       "    /* Redefinition of color scheme for dark theme */\n",
       "    --sklearn-color-text-on-default-background: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-background: var(--sg-background-color, var(--theme-background, var(--jp-layout-color0, #111)));\n",
       "    --sklearn-color-border-box: var(--sg-text-color, var(--theme-code-foreground, var(--jp-content-font-color1, white)));\n",
       "    --sklearn-color-icon: #878787;\n",
       "  }\n",
       "}\n",
       "\n",
       "#sk-container-id-1 {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 pre {\n",
       "  padding: 0;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-hidden--visually {\n",
       "  border: 0;\n",
       "  clip: rect(1px 1px 1px 1px);\n",
       "  clip: rect(1px, 1px, 1px, 1px);\n",
       "  height: 1px;\n",
       "  margin: -1px;\n",
       "  overflow: hidden;\n",
       "  padding: 0;\n",
       "  position: absolute;\n",
       "  width: 1px;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-dashed-wrapped {\n",
       "  border: 1px dashed var(--sklearn-color-line);\n",
       "  margin: 0 0.4em 0.5em 0.4em;\n",
       "  box-sizing: border-box;\n",
       "  padding-bottom: 0.4em;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-container {\n",
       "  /* jupyter's `normalize.less` sets `[hidden] { display: none; }`\n",
       "     but bootstrap.min.css set `[hidden] { display: none !important; }`\n",
       "     so we also need the `!important` here to be able to override the\n",
       "     default hidden behavior on the sphinx rendered scikit-learn.org.\n",
       "     See: https://github.com/scikit-learn/scikit-learn/issues/21755 */\n",
       "  display: inline-block !important;\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-text-repr-fallback {\n",
       "  display: none;\n",
       "}\n",
       "\n",
       "div.sk-parallel-item,\n",
       "div.sk-serial,\n",
       "div.sk-item {\n",
       "  /* draw centered vertical line to link estimators */\n",
       "  background-image: linear-gradient(var(--sklearn-color-text-on-default-background), var(--sklearn-color-text-on-default-background));\n",
       "  background-size: 2px 100%;\n",
       "  background-repeat: no-repeat;\n",
       "  background-position: center center;\n",
       "}\n",
       "\n",
       "/* Parallel-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item::after {\n",
       "  content: \"\";\n",
       "  width: 100%;\n",
       "  border-bottom: 2px solid var(--sklearn-color-text-on-default-background);\n",
       "  flex-grow: 1;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel {\n",
       "  display: flex;\n",
       "  align-items: stretch;\n",
       "  justify-content: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  position: relative;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:first-child::after {\n",
       "  align-self: flex-end;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:last-child::after {\n",
       "  align-self: flex-start;\n",
       "  width: 50%;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-parallel-item:only-child::after {\n",
       "  width: 0;\n",
       "}\n",
       "\n",
       "/* Serial-specific style estimator block */\n",
       "\n",
       "#sk-container-id-1 div.sk-serial {\n",
       "  display: flex;\n",
       "  flex-direction: column;\n",
       "  align-items: center;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  padding-right: 1em;\n",
       "  padding-left: 1em;\n",
       "}\n",
       "\n",
       "\n",
       "/* Toggleable style: style used for estimator/Pipeline/ColumnTransformer box that is\n",
       "clickable and can be expanded/collapsed.\n",
       "- Pipeline and ColumnTransformer use this feature and define the default style\n",
       "- Estimators will overwrite some part of the style using the `sk-estimator` class\n",
       "*/\n",
       "\n",
       "/* Pipeline and ColumnTransformer style (default) */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable {\n",
       "  /* Default theme specific background. It is overwritten whether we have a\n",
       "  specific estimator or a Pipeline/ColumnTransformer */\n",
       "  background-color: var(--sklearn-color-background);\n",
       "}\n",
       "\n",
       "/* Toggleable label */\n",
       "#sk-container-id-1 label.sk-toggleable__label {\n",
       "  cursor: pointer;\n",
       "  display: flex;\n",
       "  width: 100%;\n",
       "  margin-bottom: 0;\n",
       "  padding: 0.5em;\n",
       "  box-sizing: border-box;\n",
       "  text-align: center;\n",
       "  align-items: start;\n",
       "  justify-content: space-between;\n",
       "  gap: 0.5em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label .caption {\n",
       "  font-size: 0.6rem;\n",
       "  font-weight: lighter;\n",
       "  color: var(--sklearn-color-text-muted);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:before {\n",
       "  /* Arrow on the left of the label */\n",
       "  content: \"▸\";\n",
       "  float: left;\n",
       "  margin-right: 0.25em;\n",
       "  color: var(--sklearn-color-icon);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 label.sk-toggleable__label-arrow:hover:before {\n",
       "  color: var(--sklearn-color-text);\n",
       "}\n",
       "\n",
       "/* Toggleable content - dropdown */\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content {\n",
       "  max-height: 0;\n",
       "  max-width: 0;\n",
       "  overflow: hidden;\n",
       "  text-align: left;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content pre {\n",
       "  margin: 0.2em;\n",
       "  border-radius: 0.25em;\n",
       "  color: var(--sklearn-color-text);\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-toggleable__content.fitted pre {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~div.sk-toggleable__content {\n",
       "  /* Expand drop-down */\n",
       "  max-height: 200px;\n",
       "  max-width: 100%;\n",
       "  overflow: auto;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 input.sk-toggleable__control:checked~label.sk-toggleable__label-arrow:before {\n",
       "  content: \"▾\";\n",
       "}\n",
       "\n",
       "/* Pipeline/ColumnTransformer-specific style */\n",
       "\n",
       "#sk-container-id-1 div.sk-label input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator-specific style */\n",
       "\n",
       "/* Colorize estimator box */\n",
       "#sk-container-id-1 div.sk-estimator input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted input.sk-toggleable__control:checked~label.sk-toggleable__label {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label label.sk-toggleable__label,\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  /* The background is the default theme color */\n",
       "  color: var(--sklearn-color-text-on-default-background);\n",
       "}\n",
       "\n",
       "/* On hover, darken the color of the background */\n",
       "#sk-container-id-1 div.sk-label:hover label.sk-toggleable__label {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "/* Label box, darken color on hover, fitted */\n",
       "#sk-container-id-1 div.sk-label.fitted:hover label.sk-toggleable__label.fitted {\n",
       "  color: var(--sklearn-color-text);\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Estimator label */\n",
       "\n",
       "#sk-container-id-1 div.sk-label label {\n",
       "  font-family: monospace;\n",
       "  font-weight: bold;\n",
       "  display: inline-block;\n",
       "  line-height: 1.2em;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-label-container {\n",
       "  text-align: center;\n",
       "}\n",
       "\n",
       "/* Estimator-specific */\n",
       "#sk-container-id-1 div.sk-estimator {\n",
       "  font-family: monospace;\n",
       "  border: 1px dotted var(--sklearn-color-border-box);\n",
       "  border-radius: 0.25em;\n",
       "  box-sizing: border-box;\n",
       "  margin-bottom: 0.5em;\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-0);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-0);\n",
       "}\n",
       "\n",
       "/* on hover */\n",
       "#sk-container-id-1 div.sk-estimator:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-2);\n",
       "}\n",
       "\n",
       "#sk-container-id-1 div.sk-estimator.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-2);\n",
       "}\n",
       "\n",
       "/* Specification for estimator info (e.g. \"i\" and \"?\") */\n",
       "\n",
       "/* Common style for \"i\" and \"?\" */\n",
       "\n",
       ".sk-estimator-doc-link,\n",
       "a:link.sk-estimator-doc-link,\n",
       "a:visited.sk-estimator-doc-link {\n",
       "  float: right;\n",
       "  font-size: smaller;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1em;\n",
       "  height: 1em;\n",
       "  width: 1em;\n",
       "  text-decoration: none !important;\n",
       "  margin-left: 0.5em;\n",
       "  text-align: center;\n",
       "  /* unfitted */\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted,\n",
       "a:link.sk-estimator-doc-link.fitted,\n",
       "a:visited.sk-estimator-doc-link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "div.sk-estimator:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link:hover,\n",
       ".sk-estimator-doc-link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "div.sk-estimator.fitted:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover,\n",
       "div.sk-label-container:hover .sk-estimator-doc-link.fitted:hover,\n",
       ".sk-estimator-doc-link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "/* Span, style for the box shown on hovering the info icon */\n",
       ".sk-estimator-doc-link span {\n",
       "  display: none;\n",
       "  z-index: 9999;\n",
       "  position: relative;\n",
       "  font-weight: normal;\n",
       "  right: .2ex;\n",
       "  padding: .5ex;\n",
       "  margin: .5ex;\n",
       "  width: min-content;\n",
       "  min-width: 20ex;\n",
       "  max-width: 50ex;\n",
       "  color: var(--sklearn-color-text);\n",
       "  box-shadow: 2pt 2pt 4pt #999;\n",
       "  /* unfitted */\n",
       "  background: var(--sklearn-color-unfitted-level-0);\n",
       "  border: .5pt solid var(--sklearn-color-unfitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link.fitted span {\n",
       "  /* fitted */\n",
       "  background: var(--sklearn-color-fitted-level-0);\n",
       "  border: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "\n",
       ".sk-estimator-doc-link:hover span {\n",
       "  display: block;\n",
       "}\n",
       "\n",
       "/* \"?\"-specific style due to the `<a>` HTML tag */\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link {\n",
       "  float: right;\n",
       "  font-size: 1rem;\n",
       "  line-height: 1em;\n",
       "  font-family: monospace;\n",
       "  background-color: var(--sklearn-color-background);\n",
       "  border-radius: 1rem;\n",
       "  height: 1rem;\n",
       "  width: 1rem;\n",
       "  text-decoration: none;\n",
       "  /* unfitted */\n",
       "  color: var(--sklearn-color-unfitted-level-1);\n",
       "  border: var(--sklearn-color-unfitted-level-1) 1pt solid;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted {\n",
       "  /* fitted */\n",
       "  border: var(--sklearn-color-fitted-level-1) 1pt solid;\n",
       "  color: var(--sklearn-color-fitted-level-1);\n",
       "}\n",
       "\n",
       "/* On hover */\n",
       "#sk-container-id-1 a.estimator_doc_link:hover {\n",
       "  /* unfitted */\n",
       "  background-color: var(--sklearn-color-unfitted-level-3);\n",
       "  color: var(--sklearn-color-background);\n",
       "  text-decoration: none;\n",
       "}\n",
       "\n",
       "#sk-container-id-1 a.estimator_doc_link.fitted:hover {\n",
       "  /* fitted */\n",
       "  background-color: var(--sklearn-color-fitted-level-3);\n",
       "}\n",
       "</style><div id=\"sk-container-id-1\" class=\"sk-top-container\"><div class=\"sk-text-repr-fallback\"><pre>StandardScaler()</pre><b>In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook. <br />On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.</b></div><div class=\"sk-container\" hidden><div class=\"sk-item\"><div class=\"sk-estimator fitted sk-toggleable\"><input class=\"sk-toggleable__control sk-hidden--visually\" id=\"sk-estimator-id-1\" type=\"checkbox\" checked><label for=\"sk-estimator-id-1\" class=\"sk-toggleable__label fitted sk-toggleable__label-arrow\"><div><div>StandardScaler</div></div><div><a class=\"sk-estimator-doc-link fitted\" rel=\"noreferrer\" target=\"_blank\" href=\"https://scikit-learn.org/1.6/modules/generated/sklearn.preprocessing.StandardScaler.html\">?<span>Documentation for StandardScaler</span></a><span class=\"sk-estimator-doc-link fitted\">i<span>Fitted</span></span></div></label><div class=\"sk-toggleable__content fitted\"><pre>StandardScaler()</pre></div> </div></div></div></div>"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 4
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 构建数据集",
   "id": "f2857f63393d6302"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-08T02:28:49.170256Z",
     "start_time": "2025-02-08T02:28:49.158300Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.data import Dataset\n",
    "\n",
    "\n",
    "class HousingDataset(Dataset):\n",
    "    def __init__(self, mode='train'):\n",
    "        self.x, self.y = dataset_maps[mode]\n",
    "        self.x = torch.from_numpy(scaler.transform(self.x)).float()\n",
    "        self.y = torch.from_numpy(self.y).float().reshape(-1, 1)\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.x)\n",
    "\n",
    "    def __getitem__(self, idx):\n",
    "        return self.x[idx], self.y[idx]\n",
    "\n",
    "\n",
    "train_ds = HousingDataset(\"train\")\n",
    "valid_ds = HousingDataset(\"valid\")\n",
    "test_ds = HousingDataset(\"test\")"
   ],
   "id": "c7a6ac0421e8ff25",
   "outputs": [],
   "execution_count": 5
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-08T02:28:59.446737Z",
     "start_time": "2025-02-08T02:28:59.441739Z"
    }
   },
   "cell_type": "code",
   "source": "train_ds[1]",
   "id": "9355dc9d8db8bad0",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "(tensor([-0.2981,  0.3523, -0.1092, -0.2506, -0.0341, -0.0060,  1.0806, -1.0611]),\n",
       " tensor([1.5140]))"
      ]
     },
     "execution_count": 6,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 6
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# DataLoader",
   "id": "bfdef447b13c1345"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-08T02:29:19.227589Z",
     "start_time": "2025-02-08T02:29:19.224113Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torch.utils.data import DataLoader\n",
    "\n",
    "batch_size = 256\n",
    "train_loader = DataLoader(train_ds, batch_size=batch_size, shuffle=True)\n",
    "val_loader = DataLoader(valid_ds, batch_size=batch_size, shuffle=False)\n",
    "test_loader = DataLoader(test_ds, batch_size=batch_size, shuffle=False)"
   ],
   "id": "484bf81fbde68f82",
   "outputs": [],
   "execution_count": 7
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 模型定义",
   "id": "a7a1e7f890ba876d"
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 自定义层",
   "id": "7ecccbbc1acc9d03"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-08T02:33:50.228980Z",
     "start_time": "2025-02-08T02:33:50.224981Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class CustomizedLinear(nn.Module):\n",
    "    def __init__(self, in_features, out_features):\n",
    "        super().__init__()\n",
    "        # 实现初始化方法\n",
    "\n",
    "        # nn.Parameter 使其在训练中可更新\n",
    "        self.weights = nn.Parameter(torch.zeros(in_features, out_features))  # 定义权重\n",
    "        self.weights = nn.init.xavier_normal_(self.weights)  # 使用xavier_normal初始化\n",
    "        self.bias = nn.Parameter(torch.zeros(out_features))  #Parameter是nn.Module的子类，可以被自动求导，可以被优化器优化\n",
    "\n",
    "    def forward(self, x):\n",
    "        return torch.mm(x, self.weights) + self.bias  # 全连接层的前向计算,mm是矩阵乘法"
   ],
   "id": "91d979145c978f3c",
   "outputs": [],
   "execution_count": 8
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-08T02:33:56.140677Z",
     "start_time": "2025-02-08T02:33:56.136738Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#回归模型我们只需要1个数\n",
    "\n",
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, input_dim=8):\n",
    "        super().__init__()\n",
    "        self.linear_relu_stack = nn.Sequential(\n",
    "            CustomizedLinear(input_dim, 30),\n",
    "            nn.ReLU(),\n",
    "            CustomizedLinear(30, 1)\n",
    "        )\n",
    "\n",
    "    def forward(self, x):\n",
    "        # x.shape [batch size, 8]\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        # logits.shape [batch size, 1]\n",
    "        return logits"
   ],
   "id": "cecb040173417389",
   "outputs": [],
   "execution_count": 9
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-08T02:34:01.650858Z",
     "start_time": "2025-02-08T02:34:01.644742Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model = NeuralNetwork()\n",
    "for name, param in model.named_parameters():\n",
    "    print(name, param.shape)"
   ],
   "id": "492a8b647fe712a5",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "linear_relu_stack.0.weights torch.Size([8, 30])\n",
      "linear_relu_stack.0.bias torch.Size([30])\n",
      "linear_relu_stack.2.weights torch.Size([30, 1])\n",
      "linear_relu_stack.2.bias torch.Size([1])\n"
     ]
    }
   ],
   "execution_count": 10
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-08T02:34:10.069076Z",
     "start_time": "2025-02-08T02:34:10.066061Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        \"\"\"\n",
    "\n",
    "        Args:\n",
    "            patience (int, optional): Number of epochs with no improvement after which training will be stopped.. Defaults to 5.\n",
    "            min_delta (float, optional): Minimum change in the monitored quantity to qualify as an improvement, i.e. an absolute \n",
    "                change of less than min_delta, will count as no improvement. Defaults to 0.01.\n",
    "        \"\"\"\n",
    "        self.patience = patience\n",
    "        self.min_delta = min_delta\n",
    "        self.best_metric = -1\n",
    "        self.counter = 0\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:\n",
    "            # update best metric\n",
    "            self.best_metric = metric\n",
    "            # reset counter \n",
    "            self.counter = 0\n",
    "        else:\n",
    "            self.counter += 1\n",
    "\n",
    "    @property\n",
    "    def early_stop(self):\n",
    "        return self.counter >= self.patience\n"
   ],
   "id": "35ba7013d7308704",
   "outputs": [],
   "execution_count": 11
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-08T02:34:13.650067Z",
     "start_time": "2025-02-08T02:34:13.646068Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "@torch.no_grad()\n",
    "def evaluating(model, dataloader, loss_fct):\n",
    "    loss_list = []\n",
    "    for datas, labels in dataloader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "        # 前向计算\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "    return np.mean(loss_list)\n"
   ],
   "id": "9dbbf3ee242e2065",
   "outputs": [],
   "execution_count": 12
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-08T02:34:32.375157Z",
     "start_time": "2025-02-08T02:34:19.853403Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 训练\n",
    "def training(\n",
    "        model,\n",
    "        train_loader,\n",
    "        val_loader,\n",
    "        epoch,\n",
    "        loss_fct,\n",
    "        optimizer,\n",
    "        tensorboard_callback=None,\n",
    "        save_ckpt_callback=None,\n",
    "        early_stop_callback=None,\n",
    "        eval_step=500,\n",
    "):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0\n",
    "    model.train()\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            # training\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "                # 梯度清空\n",
    "                optimizer.zero_grad()\n",
    "                # 模型前向计算\n",
    "                logits = model(datas)\n",
    "                # 计算损失\n",
    "                loss = loss_fct(logits, labels)\n",
    "                # 梯度回传\n",
    "                loss.backward()\n",
    "                # 调整优化器，包括学习率的变动等\n",
    "                optimizer.step()\n",
    "\n",
    "                loss = loss.cpu().item()\n",
    "                # record\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss, \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # evaluating\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()\n",
    "                    val_loss = evaluating(model, val_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss, \"step\": global_step\n",
    "                    })\n",
    "                    model.train()\n",
    "\n",
    "                    # 早停 Early Stop\n",
    "                    if early_stop_callback is not None:\n",
    "                        early_stop_callback(-val_loss)\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict\n",
    "\n",
    "                # udate step\n",
    "                global_step += 1\n",
    "                pbar.update(1)\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict\n",
    "\n",
    "\n",
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork()\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.MSELoss()\n",
    "# 2. 定义优化器 采用SGD\n",
    "# Optimizers specified in the torch.optim package\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 3. early stop\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=1e-3)\n",
    "\n",
    "model = model.to(device)\n",
    "record = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    val_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    ")"
   ],
   "id": "1e026225e4f1c8b",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/4600 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "14af3a59718e455da06bdfa91a930087"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 13
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-08T02:34:32.431207Z",
     "start_time": "2025-02-08T02:34:32.375157Z"
    }
   },
   "cell_type": "code",
   "source": [
    "#画线要注意的是损失是不一定在零到1之间的\n",
    "def plot_learning_curves(record_dict, sample_step=500):\n",
    "    # build DataFrame\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    # plot\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        plt.plot(train_df.index, train_df[item], label=f\"train_{item}\")\n",
    "        plt.plot(val_df.index, val_df[item], label=f\"val_{item}\")\n",
    "        plt.grid()\n",
    "        plt.legend()\n",
    "        # plt.xticks(range(0, train_df.index[-1], 10*sample_step), range(0, train_df.index[-1], 10*sample_step))\n",
    "        plt.xlabel(\"step\")\n",
    "\n",
    "        plt.show()\n",
    "\n",
    "\n",
    "plot_learning_curves(record)  #横坐标是 steps"
   ],
   "id": "2c15d8124be813db",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ],
      "image/png": "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pVRUVJhDKxnt7e2ycePGjOfVUGHTCkdLS4vpRgHgDVQsAPQQCARk5cqV8swzz8iKFSvk5z//uXznO9+R5557rtfHazg47rjj5E9/+lOP+3Q8BYDCQbAA0Cvt0jj55JPNcd1115kukaVLl5rujFgslvHYOXPmyL333isjR440gzIPVtloa2sz3SlqzZo1proxYcKErP88AHKDrhAAPWhl4gc/+IGsW7fODNa8//77Zffu3XL44YfL5MmTzYDM9evXS0NDgxm4uWjRIqmrqzMzQXTw5ubNm83Yiq9//evy3nvvpZ5XZ4B84QtfkDfeeEOWLVsm3/ve9+Tyyy9nfAXgIVQsAPSgVYfVq1fLT3/6UzMDRKsVP/nJT+TMM8+U448/3oQGvdQukCeeeEJOO+008/hrrrnGDPjUWSTjxo0z4zTSKxj69YwZM+TUU081A0B15sn111+f158VgLN8lmVZDj8nAPSg61js379fHnjggXyfCoAsov4IAAAcQ7AAAACOoSsEAAA4hooFAABwDMECAAA4hmABAAAcQ7AAAACOIVgAAADHECwAAIBjCBYAAMAxBAsAACBO+f8BAMXblkQx2HcAAAAASUVORK5CYII="
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 14
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "# 测试集",
   "id": "5e27261f9934c8af"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-02-08T02:34:42.145670Z",
     "start_time": "2025-02-08T02:34:42.117165Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model.eval()\n",
    "loss = evaluating(model, val_loader, loss_fct)\n",
    "print(f\"loss:     {loss:.4f}\")"
   ],
   "id": "cfe310aa009d8736",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "loss:     0.3646\n"
     ]
    }
   ],
   "execution_count": 15
  },
  {
   "metadata": {},
   "cell_type": "code",
   "outputs": [],
   "execution_count": null,
   "source": "",
   "id": "b64f187cbede9cbf"
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
